﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Serialization;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.Computing.Entities.ESG;
using Sustainalytics.ESGRatings.DataContract;
using Sustainalytics.ESGRatings.DataContract.ESG;
using CompanyScores = Sustainalytics.ESGRatings.Computing.Entities.CompanyScores;

namespace Sustainalytics.ESGComputing.BenchmarkTest
{
    class ESGComputingBenchmark
    {
        private string _url;

        private ConcurrentQueue<Tuple<double, double>> _timmingResults = new ConcurrentQueue<Tuple<double, double>>();

        private JsonMediaTypeFormatter _jsonMediaTypeFormatter = new JsonMediaTypeFormatter
        {
            SerializerSettings = { ContractResolver = new CamelCasePropertyNamesContractResolver() }
        };
        private static ESGRatingsOptionsDto _options;

        public ESGComputingBenchmark(string url, ESGRatingsOptionsDto options)
        {
            _url = url;
            _options = options;
        }

        public double Execute(int requestCount, bool executeSpecial = false)
        {
            try
            {
                if (executeSpecial)
                    requestCount++;
                //Setup(requestCount);
                Parallel.For(0, requestCount, (i) =>
                {
                    try
                    {
                        if (i == 0 && executeSpecial)
                            ExecuteSpecialRequest();
                        else
                            ExecuteRequest(_url, _options, true);
                    }
                    catch (Exception ex)
                    {
                        Console.Write(ex.ToString());
                    }
                });

                while (requestCount != _timmingResults.Count())
                {
                    Thread.Sleep(10);
                }

                StringBuilder sb = new StringBuilder();
                sb.Append(String.Format("Execution time,Deserialize time{0}", Environment.NewLine));
                foreach (var result in _timmingResults)
                {
                    //Console.WriteLine("Execution:{0} Deserialize:{1}", result.Item1, result.Item2);
                    sb.Append(String.Format("{0},{1}{2}", result.Item1, result.Item2, Environment.NewLine));
                }
                var execTimes = _timmingResults.Select(r => r.Item1);
                var deserTimes = _timmingResults.Select(r => r.Item2);

                sb.Append(String.Format("AVG Execution,AVG Deserialize{0}", Environment.NewLine));
                sb.Append(String.Format("{0},{1}{2}", execTimes.Average(), deserTimes.Average(), Environment.NewLine));
                sb.Append(String.Format("MAX Execution,MAX Deserialize{0}", Environment.NewLine));
                sb.Append(String.Format("{0},{1}{2}", execTimes.Max(), deserTimes.Max(), Environment.NewLine));

                File.WriteAllText(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), String.Format("Output{0}.csv", requestCount)), sb.ToString());

                return _timmingResults.Select(r => r.Item1).Max();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return -1;
            }
        }

        private void Setup(int requestCount)
        {
            HttpClient client = new HttpClient();
            client.PostAsync(_url + "/statistics",
               new ComputeStatisticsOptions() { ComputationContextDetails = String.Format("{0} parallel Calls", requestCount), ResetStatistics = false }
               , _jsonMediaTypeFormatter).Result.EnsureSuccessStatusCode();
        }

        public void ExecuteSpecialRequest()
        {
            Thread.Sleep(200);
            var testSet = new ESGDataContainer
            {
                IndicatorTemplates = GetIndicatorConfiguration(),
                Companies = GetCorporateData().ToArray(),
                CompaniesScores =
                    GetIndicatorsData()
                        .Select(
                            x =>
                                new CompanyScores(x.CompanyId,
                                    x.Data.Select(y => new IndicatorDefaultScore(y.Key, (int)y.Value))))
                        .ToArray()
            };


            var matrixWeights = from weight in GetCompanyWeightsData()
                                select
                                    new CompanyWeights
                                    {
                                        CompanyId = weight.CompanyId,
                                        IndicatorsData = weight.IndicatorsData.Select(x => new Weight(x.Code, x.Value))
                                    };

            testSet.Matrices = new[]
                {
                    new WeightMatrix
                    {
                        AccountId = Guid.NewGuid(),
                        MatrixId = _matrixId,
                        CompaniesWeights = matrixWeights.ToArray()
                    }
                };

            testSet.ReferenceUniverses = new[] { GetReferenceUniverse() };

            ExecuteRequest(_sutLink, testSet, false);
        }

        private void ExecuteRequest<T>(string url, T options, bool desync)
        {
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var startExecutionTime = DateTime.Now;
            var task = client.PostAsync(url, options, _jsonMediaTypeFormatter).ContinueWith(response =>
            {
                try
                {
                    var endExecutionTime = DateTime.Now;
                    if (!response.Result.IsSuccessStatusCode)
                    {
                        _timmingResults.Enqueue(new Tuple<double, double>(-1, -1));
                        return;
                    }

                    var startDeserializationTime = DateTime.Now;
                    if (desync)
                    {
                        var esgRatingsDto = response.Result.Content.ReadAsAsync<T>(new List<JsonMediaTypeFormatter> { _jsonMediaTypeFormatter }).Result;
                    }
                    var endDeserializationTime = DateTime.Now;

                    var deserializeTime = endDeserializationTime - endExecutionTime;
                    var executionTime = endExecutionTime - startExecutionTime;

                    _timmingResults.Enqueue(new Tuple<double, double>(executionTime.TotalMilliseconds, deserializeTime.TotalMilliseconds));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    _timmingResults.Enqueue(new Tuple<double, double>(-2, -2));
                }
            });

        }

        private IEnumerable<CompanyWeights> GetCompanyWeightsData()
        {
            return new List<CompanyWeights>
                {
                    new CompanyWeights
                    {
                        CompanyId = _companyIds[0],
                        IndicatorsData = new Dictionary<string, float>
                        {
                            {"E.1.1", 0.1f},
                            {"E.1.2", 0},
                            {"E.1.12", 0.15f},
                            {"E.2.2", 0},
                            {"E.1.4", 0},
                            {"G.1.4", 0},
                            {"G.1.1", 0.2f},
                            {"S.1.1", 0.25f},
                            {"S.1.2", 0.3f}
                        }.Select(x => new Weight(x.Key, x.Value))
                    },
                    new CompanyWeights
                    {
                        CompanyId = _companyIds[2],
                        IndicatorsData = new Dictionary<string, float>
                        {
                            {"E.1.1", 0},
                            {"E.1.2", 0},
                            {"E.1.12", 0.1f},
                            {"E.2.2", 0.15f},
                            {"E.1.4", 0.1f},
                            {"G.1.4", 0.15f},
                            {"G.1.1", 0.15f},
                            {"S.1.1", 0.23f},
                            {"S.1.2", 0.12f}
                        }.Select(x => new Weight(x.Key, x.Value))
                    },
                    new CompanyWeights
                    {
                        CompanyId = _companyIds[1],
                        IndicatorsData = new Dictionary<string, float>
                        {
                            {"E.1.1", 0},
                            {"E.1.2", 0.05f},
                            {"E.1.12", 0.1f},
                            {"E.2.2", 0f},
                            {"E.1.4", 0.00f},
                            {"G.1.4", 0.1f},
                            {"G.1.1", 0.2f},
                            {"S.1.1", 0.25f},
                            {"S.1.2", 0.3f}
                        }.Select(x => new Weight(x.Key, x.Value))
                    }
                };
        }

        private IEnumerable<CompanyData> GetCorporateData()
        {
            var companies = new List<CompanyData>
                {
                    new CompanyData()
                    {
                        Id = _companyIds[0],
                        CompanyType = "public",TemplateType = "Type A",
                        IndustryGroup = "Banks",
                        PeerGroup = "Banks"
                    },
                    new CompanyData
                    {
                        Id = _companyIds[1],
                         CompanyType = "public",TemplateType = "Type A",
                        IndustryGroup = "Diversified Financials",
                        PeerGroup = "Diversified Financials"
                    }
                    ,
                    new CompanyData
                    {
                        Id = _companyIds[2],
                         CompanyType = "public",TemplateType = "Type A",
                        IndustryGroup = "Diversified Financials",
                        PeerGroup = "Diversified Financials"
                    }
                };
            return companies;
        }

        private ESGRatings.Computing.Entities.IndicatorTemplate[] GetIndicatorConfiguration()
        {
            ESGRatings.Computing.Entities.IndicatorTemplate[] builder =
            {
                    new ESGRatings.Computing.Entities.IndicatorTemplate("E.1.1", "E", "P​​"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("E.1.2",
                        "E", "P​​"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("E.1.12",
                        "E", "QLP"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("E.2.2",
                        "E", "QLP"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("E.1.4",
                        "E", "QNP"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("G.1.4",
                        "G", "D"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("G.1.1",
                        "G", "P​​"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("S.1.1",
                        "S", "P​​"),
                    new ESGRatings.Computing.Entities.IndicatorTemplate("S.1.2",
                        "S", "QLP")
                };
            return builder;
        }

        private List<IndicatorsData> GetIndicatorsData()
        {
            return new List<IndicatorsData>
                {
                    new IndicatorsData
                    {
                        CompanyId = _companyIds[0],
                        Data = new[]
                        {
                            new KeyValuePair<string, float>("E.1.1", 100),
                            new KeyValuePair<string, float>("E.1.2", 0),
                            new KeyValuePair<string, float>("E.1.12", 50),
                            new KeyValuePair<string, float>("E.2.2", 20),
                            new KeyValuePair<string, float>("E.1.4", 0),
                            new KeyValuePair<string, float>("G.1.4", 90),
                            new KeyValuePair<string, float>("G.1.1", 50),
                            new KeyValuePair<string, float>("S.1.1", 20),
                            new KeyValuePair<string, float>("S.1.2", 30)
                        }
                    },
                    new IndicatorsData
                    {
                        CompanyId = _companyIds[2],
                        Data = new[]
                        {
                            new KeyValuePair<string, float>("E.1.1", 50),
                            new KeyValuePair<string, float>("E.1.2", 40),
                            new KeyValuePair<string, float>("E.1.12", 100),
                            new KeyValuePair<string, float>("E.2.2", 100),
                            new KeyValuePair<string, float>("E.1.4", 0),
                            new KeyValuePair<string, float>("G.1.4", 80),
                            new KeyValuePair<string, float>("G.1.1", 50),
                            new KeyValuePair<string, float>("S.1.1", 20),
                            new KeyValuePair<string, float>("S.1.2", 30)
                        }
                    },
                    new IndicatorsData
                    {
                        CompanyId = _companyIds[1],
                        Data = new[]
                        {
                            new KeyValuePair<string, float>("E.1.1", 0),
                            new KeyValuePair<string, float>("E.1.2", 10),
                            new KeyValuePair<string, float>("E.1.12", 20),
                            new KeyValuePair<string, float>("E.2.2", 0),
                            new KeyValuePair<string, float>("E.1.4", 0),
                            new KeyValuePair<string, float>("G.1.4", 50),
                            new KeyValuePair<string, float>("G.1.1", 75),
                            new KeyValuePair<string, float>("S.1.1", 25),
                            new KeyValuePair<string, float>("S.1.2", 75)
                        }
                    }
                };
        }

        private ReferenceUniverse GetReferenceUniverse()
        {
            return new ReferenceUniverse
            {
                Id = _referenceUniversesId,
                Companies = new[]
                {
                        new ReferenceUniverseCompany
                        {
                            CapitalIQID = "IQ20704112",
                            CompanyId = _companyIds[0],
                            CompanyName = "Compania1"
                        },
                        new ReferenceUniverseCompany
                        {
                            CapitalIQID = "IQ20704113",
                            CompanyId = _companyIds[1],
                            CompanyName = "Compania2"
                        }
                    }
            };
        }

        public class IndicatorTemplate
        {
            public string Cluster { get; set; }

            public string Code { get; set; }

            public string Theme { get; set; }

            public IndicatorTemplate(string code, string theme, string cluster)
            {
                Cluster = cluster;
                Code = code;
                Theme = theme;
            }

            public IndicatorTemplate()
            {
            }
        }

        private readonly IList<Guid> _companyIds = new[]
        {
                new Guid("00000000-0000-0000-0000-000000000001"), new Guid("00000000-0000-0000-0000-000000000002"),
                new Guid("00000000-0000-0000-0000-000000000003")
            };

        private readonly Guid _matrixId = new Guid("00000000-0000-0000-0000-000000000004");
        private readonly Guid _referenceUniversesId = new Guid("00000000-0000-0000-0000-000000000005");
        private static readonly string _serviceLink = "http://localhost:10280/esgcomputingserv/api/v1/";
        private static readonly string _sutLink = _serviceLink + "dataprovisioning/sets";


    }
    public class IndicatorsData
    {
        public Guid CompanyId { get; set; }

        public IEnumerable<KeyValuePair<string, float>> Data { get; set; }
    }
}

